﻿using System;
using System.Windows;
using System.ComponentModel;
using System.Windows.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections.ObjectModel;

namespace SilverlightCairngorm
{
	/// <summary>
	/// modestyZ@hotmail.com: 2008.10
	/// Wrapper class that makes ObservableCollection thread-safe
	/// Any ObservableCollection that serves as DataContext for data binding in multi-threaded application should be wrapped by this type
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class BindableCollection<T> : BindableBase, INotifyCollectionChanged,
		IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
	{
		#region Constructor

		/// <summary>
		/// Turn any type that implements IEnumerable(T) into BindableCollection
		/// </summary>
		/// <param name="listData"></param>
		public BindableCollection(IEnumerable<T> listData) : base(null) 
		{
			ListSource = listData;
		}

		/// <summary>
		/// Default parameter-less constructor. It'll create an instance of inner list, or the client can set the property ListSource at any time
		/// </summary>
		public BindableCollection() : this(new ObservableCollection<T>()) { }

		public BindableCollection(ObservableCollection<T> listData) : this(listData, null) { }

		public BindableCollection(ObservableCollection<T> listData, Dispatcher dispatcher) : base(dispatcher)
        {
			InitListData(listData);
        }
        #endregion

		#region data binding events wire up

		private ObservableCollection<T> list = null;
		public IEnumerable<T> ListSource
		{
			get { return (null == list) ? null : list as IEnumerable<T>; }
			set
			{
				if (value == list)
					return;
				UninitListData();

				var listData = new ObservableCollection<T>();
				foreach (T ti in value)
					listData.Add(ti);
				InitListData(listData);
			}
		}

		private NotifyCollectionChangedEventHandler listBindingHandler = null;
		private PropertyChangedEventHandler propBindingHandler = null;

		private void InitListData(ObservableCollection<T> listData)
		{
			if (listData == null)
			{
				throw new ArgumentNullException("The list must not be null for BindableCollection InitListData");
			}

			this.list = listData;

			INotifyCollectionChanged collectionChanged = list as INotifyCollectionChanged;
			if (null == listBindingHandler)
				listBindingHandler = new NotifyCollectionChangedEventHandler(NotifyCollectionChanged);
			collectionChanged.CollectionChanged += listBindingHandler;

			INotifyPropertyChanged propertyChanged = list as INotifyPropertyChanged;
			if (null == propBindingHandler)
				propBindingHandler = new PropertyChangedEventHandler(NotifyPropertyChangedDelegate);
			propertyChanged.PropertyChanged += propBindingHandler;
		}

		private void UninitListData()
		{
			if (null == list)
				return;

			INotifyCollectionChanged collectionChanged = list as INotifyCollectionChanged;
			collectionChanged.CollectionChanged -= listBindingHandler;

			INotifyPropertyChanged propertyChanged = list as INotifyPropertyChanged;
			propertyChanged.PropertyChanged -= propBindingHandler;

			list = null;
		}

		#endregion


		#region INotifyCollectionChanged Members

		public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { };
		protected void NotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			//the dispatcher initialization could be deferred till the first PropertyChanged event raised
			CheckDispatcher();

			//check if we are on the Dispatcher thread if not switch
			if (currentDispatcher.CheckAccess())
				CollectionChanged(this, e);
			else
				currentDispatcher.BeginInvoke(new NotifyCollectionChangedEventHandler(CollectionChanged), this, e);
		}

		protected void NotifyPropertyChangedDelegate(object sender, PropertyChangedEventArgs e)
		{
			base.NotifyPropertyChanged(e.PropertyName); //base already took care of Dispatcher
		}

		#endregion

		#region ICollection<T> Members

		public void Add(T item)
		{
			list.Add(item);
		}

		public void Clear()
		{
			list.Clear();
		}

		public bool Contains(T item)
		{
			return list.Contains(item);
		}

		public void CopyTo(T[] array, int arrayIndex)
		{
			list.CopyTo(array, arrayIndex);
		}

		public int Count
		{
			get { return list.Count; }
		}

		public bool IsReadOnly
		{
			get { return (list as ICollection<T>).IsReadOnly; }
		}

		public bool Remove(T item)
		{
			return list.Remove(item);
		}

		#endregion

		#region IEnumerable<T> Members

		public IEnumerator<T> GetEnumerator()
		{
			return (list as IEnumerable<T>).GetEnumerator();
		}

		#endregion

		#region IList<T> Members

		public int IndexOf(T item)
		{
			return list.IndexOf(item);
		}

		public void Insert(int index, T item)
		{
			list.Insert(index, item);
		}

		public void RemoveAt(int index)
		{
			list.RemoveAt(index);
		}

		public T this[int index]
		{
			get
			{
				return list[index];
			}
			set
			{
				list[index] = value;
			}
		}
		#endregion

		#region ICollection Members

		void ICollection.CopyTo(Array array, int index)
		{
			((ICollection)list).CopyTo(array, index);
		}

		int ICollection.Count
		{
			get { return ((ICollection)list).Count; }
		}

		bool ICollection.IsSynchronized
		{
			get { return ((ICollection)list).IsSynchronized; }
		}

		object ICollection.SyncRoot
		{
			get { return ((ICollection)list).SyncRoot; }
		}

		#endregion

		#region IList Members

		int IList.Add(object value)
		{
			return ((IList)list).Add(value);
		}

		void IList.Clear()
		{
			((IList)list).Clear();
		}

		bool IList.Contains(object value)
		{
			return ((IList)list).Contains(value);
		}

		int IList.IndexOf(object value)
		{
			return ((IList)list).IndexOf(value);
		}

		void IList.Insert(int index, object value)
		{
			((IList)list).Insert(index, value);
		}

		bool IList.IsFixedSize
		{
			get { return ((IList)list).IsFixedSize; }
		}

		bool IList.IsReadOnly
		{
			get { return ((IList)list).IsReadOnly; }
		}

		void IList.Remove(object value)
		{
			((IList)list).Remove(value);
		}

		void IList.RemoveAt(int index)
		{
			((IList)list).RemoveAt(index);
		}

		object IList.this[int index]
		{
			get
			{
				return ((IList)list)[index];
			}
			set
			{
				((IList)list)[index] = value;
			}
		}

		#endregion

		#region IEnumerable Members

		IEnumerator IEnumerable.GetEnumerator()
		{
			return (list as IEnumerable).GetEnumerator();
		}

		#endregion
	}
}
